package info.niwota.ispy;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;

import android.content.Context;
import android.database.Cursor;
import android.provider.Settings;

/**
 * 
 * Based on
 * 
 * https://market.android.com/details?id=org.projectvoodoo.simplecarrieriqdetector
 * https://github.com/project-voodoo/simple_carrieriq_detector_app
 *
 * Added detection in system settings
 */
public class Detect {

	//private static final String TAG = "Detect";

	private Map<DetectTest, ArrayList<String>> found = new TreeMap<DetectTest, ArrayList<String>>();

    public enum DetectTest {
    	SETTINGS("System settings", 100),
        KERNEL_INTERFACES("Linux kernel interfaces", 50),
        KERNEL_DRIVERS("Linux kernel drivers", 50),
        DMESG("Linux kernel dmesg log", 100),
        ETC_CONFIG("ROM configs", 0),
        SERVICES("System services", 70),
        SYSTEM_BINARIES("ROM binaries and daemons", 70),
        RUNNING_PROCESSES("Running processes", 200),
        PACKAGES("Packages", 70),
        SUSPICIOUS_CLASSES("Suspicious classes", 0),
        LOGCAT("Android logcat debugging log", 100);

        public String name;
        public int weight;

        DetectTest(String name, int weight) {
            this.name = name;
            this.weight = weight;
        }
    }

    public void findEverything(Context ctx) {
    	findSettings(ctx);
    	//
        findKernelDevices();
        findInKallsyms();
        findDmesgStrings();
        findLogcatStrings();
        findEtcConfigText();
        findSystemBinaries();
        findSystemService();
        findRunningProcesses();
        findPotentialClasses();
        findPackages(ctx);
    }

    /*
     * Finds if applications are registered with Android by its package name
     * specifically, we are looking for commin CIQ package names.
     */

    private void findPackages(Context ctx) {
        String[] potentialPackages = {
                "com.carrieriq.iqagent",
                "com.htc.android.iqagent",
                "com.carrieriq.attrom",
                "com.carrieriq.tmobile"
        };

        ArrayList<String> lines = new ArrayList<String>();

        for (String p : potentialPackages) {
            try {
                ctx.getPackageManager().getApplicationInfo(p, 0);
                lines.add(p);
            } catch (Exception e) { 
            	//NameNotFoundException
                // if an exception is thrown that means the package was not
                // found or registered with Android
            }

        }
        found.put(DetectTest.PACKAGES, lines);
    }

    /*
     * Find kernel devices like /dev/sdio_tty_ciq_00
     */

    private void findKernelDevices() {

        String[] devicePatterns = {
                "sdio_tty_ciq.*"
        };

        String[] socketPatterns = {
                "iqbrd"
        };

        ArrayList<String> kernelStuff = new ArrayList<String>();

        try {
            for (File f : new File("/dev/").listFiles()) {

                for (String fileNamePattern : devicePatterns) {
                    if (f.getName().matches(fileNamePattern)) {
                        //Log.i(TAG, "suspicious device file found: " + f.getAbsolutePath());
                        kernelStuff.add(f.getAbsolutePath());
                    }
                }
            }
        } catch (Exception e) {
            //Log.e(TAG, "Unable to list /dev");
            e.getStackTrace();
        }

        try {
            for (File f : new File("/dev/socket").listFiles()) {

                for (String fileNamePattern : socketPatterns) {
                    if (f.getName().matches(fileNamePattern)) {
                        //Log.i(TAG, "suspicious socket found: " + f.getAbsolutePath());
                        kernelStuff.add(f.getAbsolutePath());
                    }
                }
            }
        } catch (Exception e) {
            //Log.e(TAG, "Unable to list /dev sockets");
            e.getStackTrace();
        }

        found.put(DetectTest.KERNEL_INTERFACES, kernelStuff);
    }

    /*
     * Find kernel drivers implementations
     */

    private void findInKallsyms() {

        String[] elements = {
                    "_ciq_",
            };

        ArrayList<String> lines = findInFile("/proc/kallsyms", elements);

        found.put(DetectTest.KERNEL_DRIVERS, lines);

    }

    /*
     * Search in dmesg Linux kernel log
     */

    private void findDmesgStrings() {

        String[] elements = {
                "iq.logging",
                "iq.service",
                "iq.cadet",
                "iq.bridge",
                "SDIO_CIQ",
                //"carrieriq",
                "ttyCIQ",
                "iqagent"
        };

        ArrayList<String> lines = findInCommandOutput("dmesg", elements);

        found.put(DetectTest.DMESG, lines);
    }

    /*
     * Android debugging Logcat might be full of Carrier IQ stuff or not.
     */

    private void findLogcatStrings() {
    	ArrayList<String> lines = findLog();
        found.put(DetectTest.LOGCAT, lines);
    }
    
    public static String[] LOGCAT_ELEMENTS = {
            "AppWatcherCIQ",
            "IQService",
            "IQBridge",
            "IQClient",
            "IQ_METRIC",
            "_CIQ",
            "IQ Agent",
            "iqagent",
            "KernelPanicCiqBroadcastReceiver",
            "ToCIQ",
            "submitAL34",
            "AgentService_J",
            "com.carrieriq.",
            "com/carrieriq/",
            ".iqd",
            " CIQ" //browser
    };
    
    public static String[] ELEMENTS_EXCLUDE = {
    	 "SimpleCarrierIQDetector",
         "projectvoodoo"
    };
    
    private static ArrayList<String> findLog() {
        return findInCommandOutput("logcat -d", LOGCAT_ELEMENTS);
    }
    
//    public static boolean checkLog() {
//        return checkCommandOutput("logcat -d", LOGCAT_ELEMENTS);
//    }

    /*
     * Carrier IQ can be configured by text files
     */

    private void findEtcConfigText() {
        ArrayList<String> filesList = new ArrayList<String>();
        ArrayList<String> stringsFound = new ArrayList<String>();

        String[] elements = {
                "enableCIQ",
        };

        findFiles("/etc/", ".*.txt", filesList);
        for (String filename : filesList) {
            //Log.d(TAG, "txt file for analysis found: " + filename);
            stringsFound.addAll(findInFile(filename, elements));
        }

        found.put(DetectTest.ETC_CONFIG, stringsFound);
    }

    /*
     * Carrier IQ is implemented as system binary daemon
     */

    private void findSystemBinaries() {
        ArrayList<String> filesList = new ArrayList<String>();

        String[] elements = {
                "iqmsd",
                "libiq_.*",
                "iqbridged"
        };

        findFiles("/system", elements, filesList);

        found.put(DetectTest.SYSTEM_BINARIES, filesList);
    }

    /*
     * There might be a dedicated system service running
     */

    private void findSystemService() {
        String[] elements = {
                "carrieriq",
        };
        ArrayList<String> lines = findInCommandOutput("service list", elements);

        found.put(DetectTest.SERVICES, lines);
    }

    /*
     * Find stuff in running process
     */

    private void findRunningProcesses() {

        String[] elements = {
                "iqmsd",
                "iqbridged",
                ".iqd",
                "/iqd"
                //"iqd"
        };

        ArrayList<String> lines = findInCommandOutput("ps", elements);

        found.put(DetectTest.RUNNING_PROCESSES, lines);
    }

    /*
     * Try to call certain classes directly
     */

    private void findPotentialClasses() {

        String[] classes = {
                "com.carrieriq.iqagent.service.receivers.BootCompletedReceiver",
                "com.carrieriq.iqagent.IQService"
        };

        ArrayList<String> lines = new ArrayList<String>();

        for (String suspiciousClass : classes) {
            try {
                Class.forName(suspiciousClass);

                // no error here, that means we found the class!
                lines.add(suspiciousClass);

            } catch (Exception e) {
                // exception here is a good thing, it means the offending
                // class is not existing in the system
            }
        }
        found.put(DetectTest.SUSPICIOUS_CLASSES, lines);
    }
    
    public int getDetectionScore() {

        int score = 0;

        for (DetectTest test : found.keySet()) {
            if (found.get(test).size() > 0) {
                //Log.d(TAG, "Increase detection score by confidence level " + test.weight
                //       + " for " + test);
                score += test.weight;
            }
        }

        return score;
    }
    
	/*
	 * Find in system settings
	 */
	private void findSettings(final Context ctx) {
		ArrayList<String> lines = new ArrayList<String>();
		Cursor c = null;
		try {
			c = ctx.getContentResolver().query(Settings.System.CONTENT_URI,
					new String[]{Settings.System.NAME, Settings.System.VALUE}, null, null, Settings.System.NAME + " ASC");
			if (c != null) {
				findSettings(c, lines);
			}
			
			c = ctx.getContentResolver().query(Settings.Secure.CONTENT_URI,
					new String[]{Settings.System.NAME, Settings.System.VALUE}, null, null, Settings.Secure.NAME + " ASC");
			if (c != null) {
				findSettings(c, lines);
			}
			
			found.put(DetectTest.SETTINGS, lines);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private void findSettings(final Cursor c, final ArrayList<String> lines) {
		final String[] elements = { "iqagent_logging" };
		StringBuilder sb = new StringBuilder();
		try {
			while (c.moveToNext()) {
				sb.setLength(0);
				for (int i = 0; i < c.getColumnNames().length; i++) {
					String v = c.getString(i);
					sb.append(v);
					sb.append("\t");
				}
				String line = sb.toString();
				if (contains(line, elements)) {
					lines.add(line);
				}
			}
		} finally {
			if (c != null)
				c.close();
		}
	}
	
	public static boolean contains(String line, String[] elements) {
		for (String pattern : elements) {
            if (line.contains(pattern)) {
                return true;
            }
		}
		return false;
	}
	
	public static boolean isExcluded(String line) {
		return contains(line, ELEMENTS_EXCLUDE);
	}
	
	//
	public Map<DetectTest, ArrayList<String>> getFound() {
		return found;
	}
	
	//utils

    public static final String getCommandOutput(String command) {

        StringBuilder output = new StringBuilder();

        try {

            Process p = Runtime.getRuntime().exec(command);
            InputStream is = p.getInputStream();
            InputStreamReader r = new InputStreamReader(is);
            BufferedReader in = new BufferedReader(r);

            String line;
            while ((line = in.readLine()) != null) {
                output.append(line);
                output.append("\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
            //Log.e(TAG, "Error when running " + command);
        }

        return output.toString();
    }

    public static final ArrayList<String> findInCommandOutput(String command, String[] elements) {

        ArrayList<String> lines = new ArrayList<String>();

        try {
            Process p = Runtime.getRuntime().exec(command);
            BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));

            String line;
            while ((line = in.readLine()) != null) {
                for (String pattern : elements) {
                    if (line.contains(pattern)
                            && !line.contains("SimpleCarrierIQDetector")
                            && !line.contains("projectvoodoo")) {
                        lines.add(line);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            //Log.e(TAG, "Unable to analyze " + command);
        }

        return lines;
    }
    
//    public static final boolean checkCommandOutput(String command, String[] elements) {
//        try {
//            Process p = Runtime.getRuntime().exec(command);
//            BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
//
//            String line;
//            while ((line = in.readLine()) != null) {
//
//                for (String pattern : elements) {
//                    if (line.contains(pattern)
//                            && !line.contains("SimpleCarrierIQDetector")
//                            && !line.contains("projectvoodoo")) {
//                       return true;
//                    }
//                }
//            }
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//
//        return false;
//    }

    public static final ArrayList<String> findInFile(String filename, String[] elements) {
        String line;
        ArrayList<String> lines = new ArrayList<String>();

        try {
            FileInputStream fstream = new FileInputStream(filename);
            BufferedReader in = new BufferedReader(new InputStreamReader(fstream));

            while ((line = in.readLine()) != null) {
                for (String pattern : elements)
                    if (line.contains(pattern))
                        lines.add(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
           //Log.e(TAG, "Unable to analyze " + filename);
        }

        return lines;

    }

    public static void findFiles(String path, String pattern, ArrayList<String> fileList) {
        String[] patterns = {
                pattern,
        };
        findFiles(path, patterns, fileList);
    }

    public static void findFiles(String path, String[] patterns, ArrayList<String> fileList) {

        File root = new File(path);
        File[] list = root.listFiles();

        try {
            for (File f : list) {

                if (f.isDirectory())
                    findFiles(f.getAbsolutePath(), patterns, fileList);
                else
                    for (String pattern : patterns)
                        if (f.getName().matches(pattern))
                            fileList.add(f.getAbsolutePath());

            }
        } catch (Exception e) {
            e.printStackTrace();
           //Log.d(TAG, "Unable to list path: " + path);
        }
    }
}
